/*
 * Created on 2007-7-2
 *
 */
package jacky.lanlan.song;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class CommonTestUtils {
	
	private static Map<Class,Method> fieldMethodMapping=new ConcurrentHashMap<Class,Method>(); //Field所有的Getter方法缓存
	private static Map<Class,Object> primitiveDefaultValue=new ConcurrentHashMap<Class,Object>();
	
	static {
		for(Method m:Field.class.getMethods()) {
			if(m.getName().startsWith("get") && m.getParameterTypes().length==1) {//所有带一个参数的get方法
				fieldMethodMapping.put(m.getReturnType(), m);
			}
		}
		
		primitiveDefaultValue.put(Integer.TYPE, 0);
		primitiveDefaultValue.put(Float.TYPE, 0f);
		primitiveDefaultValue.put(Double.TYPE, 0d);
		primitiveDefaultValue.put(Long.TYPE, 0l);
		primitiveDefaultValue.put(Short.TYPE, 0);
		primitiveDefaultValue.put(Character.TYPE, 0);
		primitiveDefaultValue.put(Byte.TYPE, 0);
		primitiveDefaultValue.put(Boolean.TYPE, false);
	}
	
	/**
	 * 得到指定Class的指定方法，若该方法是一个private方法，则自动设置它为可访问。
	 * @param clazz 类型
	 * @param methodName 方法名，若存在同名方法，则返回的方法是不确定的
	 * @return 被反射出的方法，或者null如果没有这个方法
	 */
	public static Method refMethod(Class clazz,String methodName) {
		Method method=null;
		for(Method m:clazz.getDeclaredMethods()) {//先找自身
			if(m.getName().equals(methodName)) {
				method=m;
				break;
			}
		}
		if(method==null) {//如果没有找到
			for(Method m:clazz.getMethods()){//再找超类
				if(m.getName().equals(methodName)) {
					method=m;
					break;
				}
			}
		}
		if(method!=null && Modifier.isPrivate(method.getModifiers())) {
			method.setAccessible(true);//private方法，需要设置访问权限才能访问
		}
		return method;
	}

	/**
	 * 打印指定对象的Field信息，格式：Field名->值
	 * @param obj
	 */
	public static void printFieldInfo(Object obj) {
		Set<Field> set=refFields(obj.getClass());
		int count=0;
		try {
			for(Field f:set) {
				System.out.print(f.getName()+"->"+f.get(obj));
				System.out.print("    \t\t");
				if(++count%4==0) {
					System.out.println();
				}
			}
		}
		catch (Exception e) {}
	}
	
	/**
	 * 测试指定对象的Field是否被正确赋值。
	 * <p>
	 * 客户端需要指定一个Map，用来映射Field和其期望的值。<br>
	 * <em>该方法调用equals()方法测试相等性，所以如果期望值为对象，则需要正确实现equals()方法</em>。
	 * @param obj 要测试的对象
	 * @param expectMapping 期望映射，Key=Field名字，Value=期望的值
	 * @return true 如果expectMapping和实际值相等，false 如果有至少一个不相等
	 */
	public static boolean isFieldSetted(Object obj,Map<String,Object> expectMapping) {
		Set<Field> fieldSet=refFields(obj.getClass());
		Object expectValue=null;
		Object origValue=null;
		try {
			for(Field f:fieldSet) {
				expectValue=expectMapping.get(f.getName());
				origValue=
					fieldMethodMapping.get(
						!f.getType().isPrimitive()?//如果不是基础类型，需要映射到Object.class
							Object.class : f.getType()
					).invoke(f,obj);
				if(expectValue!=null && !expectValue.equals(origValue)) {
					return false;
				}
			}
		}
		catch (Exception e) {
			throw new RuntimeException("反射调用方法时出错："+e);
		}
		return true;
	}
	
	/**
	 * 检测指定对象是否有值为null的Field(引用类型)，或者有值为初始值的基本类型Field(int，char...)，并返回这些Field。
	 * @param obj 待检测对象
	 * @return 一个装载obj所有值为null的引用类型的Field，和值为初始值的基本类型Field的Set
	 */
	public static Set<Field> isFieldsNotNull(Object obj) {
		Set<Field> set=refFields(obj.getClass());
		Set<Field> nullSet=new HashSet<Field>();
		Object value=null;
		try {
			for(Field f:set) {
				value=fieldMethodMapping.get(
						!f.getType().isPrimitive()?//如果不是基础类型，需要映射到Object.class
								Object.class : f.getType()
				).invoke(f,obj);
				if(f.getType().isPrimitive() && primitiveDefaultValue.get(f.getType())==value) {
					nullSet.add(f);
				}
				if(value==null) {
					nullSet.add(f);
				}
			}
		}
		catch (Exception e) {}
		return nullSet;
	}
	
	//反射指定Class的所有Field
	private static Set<Field> refFields(Class clazz) {
		Set<Field> set=new HashSet<Field>();
		for(Field f:clazz.getDeclaredFields()) {
			set.add(f);
			f.setAccessible(true);
		}
		for(Field f:clazz.getFields()){
			set.add(f);
			f.setAccessible(true);
		}
		return set;
	}
	
}

